package ws

import (
	"fmt"
	"reflect"

	"github.com/gogf/gf/container/gvar"
	"github.com/gogf/gf/encoding/gjson"
	"github.com/gogf/gf/util/gvalid"
)

type Base struct {
	CallBack string `json:"call_back"` // 回调地址
	Cmd      string `json:"cmd"`       // 请求命令字
	SeqId    string `json:"seq_id"`    // 消息ID,客户端请求不重复即可
}

type Request struct {
	Base     Base
	Data     []byte    // ws 接收到的消息
	Response *JsonData // ws 返回数据
}

// GetVar 获取请求参数
func (r *Request) GetVar(key string, def ...interface{}) *gvar.Var {
	j, _ := gjson.LoadJson(r.Data)
	return j.GetVar("data."+key, def...)
}

// Parse is the most commonly used function, which converts request parameters to struct or struct
// slice. It also automatically validates the struct or every element of the struct slice according
// to the validation tag of the struct.
// 1. Single struct, post content like: {"id":1, "name":"john"}
// 2. Multiple struct, post content like: [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
func (r *Request) Parse(pointer interface{}) error {
	var (
		reflectVal1  = reflect.ValueOf(pointer)
		reflectKind1 = reflectVal1.Kind()
	)
	if reflectKind1 != reflect.Ptr {
		return fmt.Errorf(
			"parameter should be type of *struct/**struct/*[]struct/*[]*struct, but got: %v",
			reflectKind1,
		)
	}
	var (
		reflectVal2  = reflectVal1.Elem()
		reflectKind2 = reflectVal2.Kind()
	)
	j, err := gjson.LoadContent(r.Data)
	if err != nil {
		return err
	}
	if !j.Contains("data") {
		return nil
	}
	switch reflectKind2 {
	// Single struct, post content like:
	// {"id":1, "name":"john"}
	case reflect.Ptr, reflect.Struct:
		// Conversion.
		if err := j.GetStruct("data", pointer); err != nil {
			return nil
		}
		// Validation.
		if err := gvalid.CheckStruct(pointer, nil); err != nil {
			return err
		}

	// Multiple struct, post content like:
	// [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
	case reflect.Array, reflect.Slice:
		// If struct slice conversion, it might post JSON/XML content,
		// so it uses gjson for the conversion.
		if err := j.GetStructs("data", pointer); err != nil {
			return nil
		}

		for i := 0; i < reflectVal2.Len(); i++ {
			if err := gvalid.CheckStruct(reflectVal2.Index(i), nil); err != nil {
				return err
			}
		}
	}
	return nil
}
